/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package neuralmusic.brain.module;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import neuralmusic.brain.ga.Token;
import neuralmusic.brain.ga.Type;

/**
 * 
 * @author pjl
 */
public class Neuron extends BasicDelayed implements Excitable, Location {

	static boolean debug = false;

	public FireObserver fireObserver;

	List<Connection> out;
	List<Connection> in;

	Point pt;
	private float delayInSecs;
	private float timeConstant;

	// STATE OF NEURON

	double lastUpdateTime;
	double state;
	private float outputVal;

	float thresh;

	

	private float floor;

	private float pulseDelta;

	public Neuron() {
		this.out = new ArrayList<Connection>();
		this.in = new ArrayList<Connection>();
		this.state = 0;
		this.pt = new Point(0, 0);

	}
	
	
	/**
	 * 
	 * @param pt
	 *            point in space (GUI)
	 * @param fireQue
	 *            queue to go on for firing
	 * @param thresh
	 *            threshold for firing
	 * @param outputVal
	 *            value when firing
	 * 
	 * @param timeConstant
	 *            decay e^{ -timeConstant*time )
	 */



	

	public Neuron(Point pt, NeuronParams pram) {
		this();
		this.pt = pt;
		this.thresh = pram.thresh;
		this.outputVal = pram.outputVal;
		this.delayInSecs = pram.delayInSecs;
		assert(delayInSecs > 0);
		this.timeConstant = pram.timeConstant;
		this.floor=pram.floor;
		this.pulseDelta=pram.pulseDelta;

	}

	// apply any pending decay due to time lasped to the state.
	public void updateState() {
		double now = sched.getCurrentTimeInSecs();
		state = state * Math.exp(-timeConstant * (now - lastUpdateTime));
		lastUpdateTime = now;
	}

	// excite the neuron with value
	
	@Override
	public void excite(float val) {

		// make the state what it should be for current time.
		updateState();

		// add the excitation
		state += val;

		// truncate if lower than the floor
		if (state < floor)
			state = floor;
		
		if (isFiring()) {
			return;
		}

			
		if (state > thresh) {
			state -= pulseDelta;
			if (state < floor)
				state = floor;

			if (debug) {
				System.out.println("queue: " + this);
			}
			fireAtDelta(delayInSecs);
		}

	}

	/*
	 * Only called from the fireThread
	 */
	@Override
	public void fireImpl(Scheduler sched) throws BrainException {

		if (debug) {
			System.out.println("fire: " + this);
		}

		if (fireObserver != null) {
			fireObserver.notify(this);
		}
		
		for (Connection c : out)
				c.excite(outputVal);
		
		// see if we still have enough juice to refire

		excite(0);
	}

	/**
	 * 
	 * @return normalized state (for the GUI)
	 */
	public float getDisplayValue() {
		updateState();
		if (isFiring()) return 1.0f;
		return (float) (state / thresh);
	}

	@Override
	public Point getPoint() {
		return pt;
	}

	void addOut(Connection c) {
		out.add(c);
	}

	@Override
	public void setPoint(Point point) {
		this.pt = point;
	}

	// public Long getId() {
	// return id;
	// }

	public List<Token> getParams(Brain brain) {
		List<Token> prams = new ArrayList<Token>();
		prams.add(new Token(Type.X, pt.x));
		prams.add(new Token(Type.Y, pt.y));
		prams.add(new Token(Type.D, delayInSecs));
		prams.add(new Token(Type.T, thresh));
		prams.add(new Token(Type.O, outputVal));
		prams.add(new Token(Type.C, timeConstant));

		int inIds[] = brain.toIntArray(in);

		prams.add(new Token(Type.INPUTS, inIds));

		int outIds[] = brain.toIntArray(out);

		prams.add(new Token(Type.OUTPUTS, outIds));

		prams.add(new Token(Type.TERM, null));

		return prams;
	}

	public void buildFromParams(List<Token> params, Brain brain) {
		for (Token tok : params) {

			Type type = tok.type;
			Object data = tok.data;

			switch (type) {
			case X:
				pt.x = (Float) data;
				break;
			case Y:
				pt.y = (Float) data;
				break;
			case D:
				delayInSecs = (Float) data;
				break;
			case T:
				thresh = (Float) data;
				break;
			case O:
				outputVal = (Float) data;
				break;
			case C:
				timeConstant = (Float) data;
				break;
			case INPUTS:
				int inId[] = (int[]) data;
				for (int i : inId) {
					Connection c = brain.getConnection(i);
					c.setSink(this);
					in.add(c);
				}
				break;
			case OUTPUTS:
				int outId[] = (int[]) data;
				for (int i : outId) {
					Connection c = brain.getConnection(i);
					out.add(c);
				}
			}
		}
		params = null;
	}

	public void addIn(Connection c) {
		in.add(c);
	}

	public List<Connection> getOutCOnnections() {
		return out;
	}





	
}
